WEBRC congestion control (RFC 3738), unverified: no conformance claimed - #98
Conversation
72a49df to
68ef5e7
Compare
45f4df6 to
e90c13d
Compare
09615ac to
b87b597
Compare
b87b597 to
f20c4ff
Compare
Problem The receiver joined one multicast group in its constructor and held it for the life of the session, with the join logic inline there and no way to reach it again. A multiple rate congestion control building block works by moving a receiver between a session's channels, so that shape makes one impossible to add. [code-derived] Basis RFC 5775 clause 2.1, on the session shape the building block assumes: "An ALC session comprises multiple channels originating at a single sender" RFC 3450 clause 2.2 requires such a building block for plain ALC, and RFC 5775 clause 2.2 names RFC 3738 for version 2. Neither is implemented here and neither is claimed. This is the prerequisite they both need, and it is useful on its own for a session announced on more than one address. Raised by reading the authority during this work Change Receiver owns the socket, so the membership belongs there. The constructor's inline join, which handled source-specific and any-source multicast over both families, is extracted into set_group_membership(group, join) and now serves leaving as well: the source-specific paths gain their IP_DROP_SOURCE_MEMBERSHIP and MCAST_LEAVE_SOURCE_GROUP counterparts, the any-source paths their leave_group(). join_channel() and leave_channel() expose it, keeping the set of joined groups so a redundant call is a no-op that reports no change rather than a socket error. The interface, and the source where the session is source-specific, are retained from construction so a later join uses the same ones. No behaviour changes for a session that never calls the new methods: the constructor takes the same path it did, through the extracted function. Verification T1: 66 cases pass, 3 new, covering the constructed group being recorded as joined, a further channel joined and left, and redundant calls reporting no change. The existing suite, including the end-to-end multicast transfers, passes unchanged, which is what shows the extraction did not alter the constructor's behaviour. Not in this change Nothing calls these. No congestion control building block, no WEBRC, no CCI content: the field is still sent as zeros, which is correct while no building block defines it. Nothing here claims conformance to RFC 3450 clause 2.2 or RFC 5775 clause 2.2.
Problem Transmitter held one socket and one destination for the life of the session, so a session could only ever have one channel. A multiple rate congestion control building block sends to several channels at different rates and lets each receiver choose how many it joins, which that shape makes impossible. [code-derived] Basis RFC 5775 clause 2.1, on the session shape such a building block assumes: "An ALC session comprises multiple channels originating at a single sender" RFC 3450 clause 2.2 requires such a building block for plain ALC and RFC 5775 clause 2.2 names RFC 3738 for version 2. Neither is implemented here and neither is claimed. This is the sending half of the prerequisite they need; the receiving half landed in the previous commit. Raised by reading the authority during this work Change add_channel() and remove_channel(), with channel 0 the one given at construction, which is WEBRC's base channel and cannot be removed: a session with no channels is not a session. An added channel takes the same socket options and source-address binding as the first, so every channel leaves from the announced source and behaves alike. Nothing sends on an added channel. The send path is untouched and still uses channel 0, so a session that never calls these behaves exactly as before, which the unchanged end-to-end transfers demonstrate. Verification T1: 69 cases pass, 3 new, covering a session starting with one channel, channels added and removed with the indices behaving as documented, and the constructed channel and a nonexistent one both refusing removal. Not in this change No sending on added channels, no rate per channel, no time slots, no CCI content: the field is still zeros, which is correct while no building block defines it. Nothing claims conformance to RFC 3450 clause 2.2 or RFC 5775 clause 2.2.
Problem Congestion control is the one mandatory obligation the library does not meet for a session outside the 3GPP profiles. Nothing of the building block existed, so there was nothing for a sender to drive channels from. [source-derived] Basis RFC 5775 clause 2.2: "At a minimum, implementations of ALC MUST support [RFC3738]." RFC 3450 clause 2.2 imposes the same obligation without naming a building block for plain ALC. RFC 3738 clause 3.1.1 gives the inputs and the quantities derived from them, and clause 3.1.2 the wave shape: "wave channel i is active during time slots i-N+1 modulo T, i-N+2 modulo T, ..., i and is quiescent for time slots i+1 modulo T, i+2 modulo T, ..., i+Q modulo T." Not needed by a 3GPP profile: TS 26.346 clause 7.2.4 excludes congestion control for MBMS download, and clause 7.2.7 fixes the CCI at a 32-bit zero. Raised by reading the authority during this work Change A new Webrc namespace holding the sender's schedule and nothing else: the derived quantities SR_P, BCR_b, L, Q and T from clause 3.1.1, which channels are active in a given time slot, and the rate of the base channel and of a wave within a slot. Pure arithmetic with no I/O, so it is testable against the clause's own formulas without a running session. Inputs are validated rather than trusted. P at exactly 1 makes L undefined through log(P), and a cycle longer than 255 slots cannot be expressed in the 8-bit channel number of the short-format Congestion Control Information, since the base channel takes the value T. Both are refused. Verification T1: 86 cases pass, 17 new. The derived quantities are checked against the clause's formulas recomputed independently in the test, including the recommended TSD of 10 and QD of 300 giving Q=30 and T=34; the wave schedule is checked for its own N slots, for wrapping around the cycle, and for exactly N wave channels being active in every slot of a full cycle; the rates for the endpoints the clause states, for continuity across a slot boundary and for strict decrease within a slot. Not in this change Nothing sends. No packet carries this: the Congestion Control Information field is still zeros. The receiver half of RFC 3738, clause 3.2, is absent entirely. Nothing here claims conformance to RFC 3738, RFC 3450 clause 2.2 or RFC 5775 clause 2.2, and the deviation the clause permits at the start of a wave to hold the aggregate rate constant is not implemented.
…ies it Problem The Congestion Control Information field was always sent as zeros, with no way for a congestion control building block to put anything in it. RFC 3738's channels cannot be told apart by a receiver without it. [code-derived] Basis RFC 3738 clause 5.1 gives the short format as three fields totalling 32 bits, which is the width the LCT header's C=0 already selects, so carrying it costs nothing the header did not spend: "CTSI indicates the index of the current time slot." "CN is the channel number that this packet belongs to." "The PSN of each packet is scoped by its CN value." Zeros remain correct where no building block is running, and are required of a 3GPP session. TS 26.346 V18.2.0 clause 7.2.7: "-The length of the CCI (Congestion Control Identifier) field shall be 32 bits and it is assigned a value of zero (C=0)." Raised by reading the authority during this work Change AlcPacket takes an optional CongestionControlInfo and writes CTSI, channel number and a network-order packet sequence number into the field when given one. Omitted, nothing is written and the already-zeroed buffer stands, so every existing caller including every 3GPP session sends exactly what it sent before. Verification T1: 89 cases pass, 3 new, covering the field left zero when absent, each byte in the order the clause lays out when present, and the 2^16-1 sequence number the clause reserves for the last packet of a wave. Not in this change Nothing supplies one. The transmitter does not yet drive channels from the schedule, so no packet the library sends carries a non-zero field. No conformance to RFC 3738 is claimed.
Problem The WEBRC schedule existed but nothing drove it: every packet still went to the session's single destination with a zero Congestion Control Information field, so a receiver had no way to tell one channel of a session from another. [code-derived] Basis RFC 5775 clause 2.2: "At a minimum, implementations of ALC MUST support [RFC3738]." RFC 3738 clause 5.1 on what each packet carries: "CTSI indicates the index of the current time slot." and "The Current Time Slot Index increases by one modulo T each TSD seconds at the sender". Clause 3.1.2 on the channel numbering: "Recall that CN = T for the base channel and CN = 0,1,...,T-1 for the wave channels." Refused under a 3GPP profile, which excludes congestion control altogether. TS 26.346 V18.2.0 clause 7.2.4: "For simplicity of congestion control, FLUTE channelization shall be provided by a single FLUTE channel with single rate transport." Raised by reading the authority during this work Change enable_webrc() takes the parameters and one address per wave channel, opens a channel for each, and starts the time slot clock. The session's own destination becomes the base channel and takes CN = T. Each packet then goes to one of the channels active in the current slot and carries the Congestion Control Information naming it, with a per-channel sequence number counting consecutively modulo 2^16. The number of addresses must match the T the parameters derive, and a mismatch is refused at setup: a session sending waves to the wrong number of channels is not the session its description announces. Verification T1: 94 cases pass, 5 new, covering refusal under both 3GPP profiles, one channel opened per wave channel with the base channel making T+1, refusal of the wrong number of addresses, the base channel taking CN = T while added channel i is wave channel i-1, and no Congestion Control Information before the building block is enabled. Not in this change Channels active in a slot are taken in turn rather than weighted by the per-slot rates of clause 3.1.2, so the wave shape is not yet reproduced on the wire; the rate functions exist and are tested but nothing calls them. The deviation clause 3.1.2 permits at the start of a wave is not implemented. The receiver half, clause 3.2, is absent. No conformance to RFC 3738, RFC 3450 clause 2.2 or RFC 5775 clause 2.2 is claimed, and none should be until the wave shape and a receiver exist and have been tested against another implementation.
Problem
The sender half of the building block ran, but nothing on the receive side decided which channels
to be joined to, which is where a multiple rate congestion control building block does its
work. [code-derived]
Basis
RFC 3738 clause 3.2.2.1 gives the loss estimate as two filters over packet and loss counts, with
the update rules stated as formulas over W, X, Y and Z and the recommended smoothing constants
Nu = 0.3 and Delta = 0.3.
RFC 3738 clause 3.2.2.2 gives the round-trip estimate: "ARTT is updated to
max{P*ARTT,(1-Rho)*ARTT+Rho*MRTT}", with Alpha recommended at 0.25.
RFC 3738 clause 3.2.2.3: "REQN = 1/(ARTT*sqrt{LOSSP}(0.816 + 7.35*LOSSP*(1+32*LOSSP^2)))"
RFC 3738 clause 3.2.2.7: "TRATE = min{max{SSR_P, REQN}, MRR_P}. When SSR_P = infinity, TRATE is
computed as TRATE = min{4*TRR_P, MRR_P}."
RFC 3738 clause 3.2.3.6 gives the join decision, including "If NWC = N the receiver MUST not
join." and the rate inequality against ARR_P.
Raised by
reading the authority during this work
Change
Webrc::ReceiverController, holding the loss and round-trip estimates, the rate equation, the
target rate and the join decision with the mandatory refusals of clause 3.2.3.6.
It performs no I/O and joins nothing. It is fed events and answers whether a join is permitted,
and the caller acts or does not. That is deliberate rather than unfinished: a rate control loop
cannot be validated against itself, and wiring it to Receiver's joins would put an unverified
control loop in charge of how much bandwidth a deployment takes.
Verification
T1: 105 cases pass, 11 new. The loss estimate is checked for staying bounded without loss and
rising with frequent loss events; the rate equation against the clause's formula recomputed in the
test; the base channel setting the round trip directly and the floor holding against a negative
measurement, which the clause says can occur; the target rate during start-up and its cap; and
each mandatory refusal of clause 3.2.3.6 separately, each shown to change the answer.
Not in this change
Nothing calls it. The optional checks of clause 3.2.3.6, on RR_P against its maximum since the
last join and on TRR_P being greatly below ARR_P, are not applied, and the constant-aggregate-rate
variant of the inequality is not distinguished, the stricter form being used. Slow start keeps
SSR_P at infinity: clause 3.2.3.4's adjustment at a loss event is absent. The reception rates of
clause 3.2.2.5 are supplied by the caller rather than measured here.
No conformance to RFC 3738, RFC 3450 clause 2.2 or RFC 5775 clause 2.2 is claimed, and none should
be until this has been tested against another implementation on a real network.
Problem The sender distributed every packet over the base channel and the active wave channels in turn, the FDT among them. A receiver that has not climbed above the base channel would then miss part of every FDT Instance, and since no object can be interpreted before the FDT describing it arrives, such a receiver would get nothing at all from the session. That is the opposite of what a congestion control building block is for: the receivers it exists to serve are exactly the ones it would have starved. [code-derived] Basis RFC 3926 clause 4, recommendation 1: "The layers to which packets for FDT Instances are sent SHOULD NOT be biased towards those layers to which lower rate receivers are not joined." The same recommendation names putting every FDT packet in the lowest layer as an acceptable way to satisfy it, which is what this does. Its recommendation 2, asking for an FEC scheme other than Encoding ID 0 where FDT packets do reach layers a lower rate receiver does not, is thereby moot; this sender always carries the FDT as Compact No-Code, so keeping it on the base channel is what makes that safe. Raised by reading the authority during this work Change An object with TOI 0 goes to the base channel and nowhere else. Content objects continue to spread over the base channel and the waves active in the current time slot. Verification T1: 106 cases pass, 1 new, checking that the base channel's Congestion Control Information carries CN = T, which is what an FDT packet rides whatever the round-robin over content channels has reached. Not in this change The round-robin over content channels is still unweighted by the per-slot rates of RFC 3738 clause 3.1.2, and the other gaps recorded on this branch are unchanged. No conformance is claimed.
Problem Packets were spread over the base channel and the active wave channels in equal turns. The schedule computes a rate per channel, and those rates differ by a factor of P per slot of a wave's remaining life, so equal turns put the wrong share on every channel and the wave shape never reached the wire at all. Only the wave schedule did. [source-derived] Basis RFC 3450 clause 4.4: "The ALC sender MUST obey the rules for filling in the CCI field in the packet headers and MUST send packets at the appropriate rates to the channels associated with the session as dictated by the multiple rate congestion control building block." RFC 3738 clause 3.1.2 is what dictates them: a wave's rate falls by a factor of P per time slot down to the base channel's rate in its last active slot, and the base channel falls from BCR_P to P*BCR_P across each slot. Raised by reading the authority during this work Change Each channel accrues credit at its own current rate and spends one credit per packet, so the share each receives converges on its share of the total rate. A wave with more active slots to run therefore takes more packets than one about to go quiescent, and both take more than the base channel, which is the shape the clause describes. The rate functions were already present and tested; this is what calls them. The FDT is unaffected and still goes to the base channel alone, for the reason in the previous commit. Verification T1: 107 cases pass, 1 new, checking the ordering the credit scheme depends on: a younger wave outruns an older one, both outrun the base channel, and one slot of age is exactly a factor of 1/P, which is the ratio the shares converge on. Not in this change The deviation RFC 3738 clause 3.1.2 permits over the first two slots of a wave, to hold the aggregate rate constant, is still not implemented; the exponential is used throughout. Slow start is still absent and nothing still calls the receiver controller. No conformance is claimed.
Problem
SSR_P was initialised to infinity and never left it, so the receiver stayed in the start-up period
for the life of the session. Its target rate was therefore always four times the target reception
rate, the rate equation never entered the decision, and a loss event lowered nothing.
[source-derived]
Basis
RFC 3738 clause 3.2.3.4: "When a start of a loss event is detected, the value of SSR_P is updated
to max{SSMINR_P, P*TRR_P}."
RFC 3738 clause 3.2.2.6, on the floor: "The recommended value for SSMINR_P is BCR_P*(1+1/P+1/P^2)."
The same clause, on the other event now implemented: "When SSR_P = infinity, if
(P^(-NWC-2)-1)/(P^(-NWC-1)-1)*ARR_P exceeds MRR_P or SR_P, the receiver MUST set SSR_P to
max{SSMINR_P, TRR_P}."
Raised by
reading the authority during this work
Change
A loss event ends the start-up period and sets the threshold to the greater of the floor and P
times the target reception rate, and every later loss lowers it again. Reaching the maximum
reception rate ends it too, checked once an epoch where the quantities it needs are already
current. The floor is exposed so a caller can see what the threshold is held to.
Verification
T1: 113 cases pass, 6 new: the threshold starting infinite, the floor matching the clause's
formula, a loss event ending start-up and setting the value the clause gives, the floor holding
when P times the target rate would be lower, the maximum reception rate ending start-up, and the
target rate switching between the two branches of clause 3.2.2.7 as a result.
Not in this change
The two remaining start-up exits of clause 3.2.2.6, a sharp increase in the round-trip measurement
and the target reception rate failing to rise after a join, are not implemented; both are phrased
as RECOMMENDED thresholds over measurements this class is given rather than takes. The reset of
the loss variables that the same clause asks for on leaving start-up is also absent. Nothing calls
the controller, and no conformance to RFC 3738 is claimed.
Problem Receiver joined every session unconditionally and never looked at the CCI field, so a non-3GPP session ran with no congestion control at all. src/Receiver.cpp, constructor. [code-derived] Basis RFC 3450 clause 4.5: "The receiver MUST process and act on the CCI field in accordance with the multiple rate congestion control building block." RFC 3450 clause 4.5: "If a receiver is not able to implement the multiple rate congestion control building block it MUST NOT join the session." RFC 3451 clause 6.2: "If a receiver is not able to implement the congestion control protocol used in the session, it MUST NOT join the session." Neither binds the 3GPP profiles, which run no congestion control. TS 26.346 V18.2.0 clause 7.2.4: "a single FLUTE channel with single rate transport" Loss detection, RFC 3738 clause 3.2.3.4: "Each time the receiver detects a lost packet (based on the sequence numbers in the packets scoped by the channel number), the receiver records the start of a new loss event" Short-format CCI layout, RFC 3738 clause 5.1. Raised by reading the authority during this work Change Receiver's constructor takes the session Profile and, for a session outside the 3GPP profiles, the WEBRC session channels. Without them it throws rather than joining, which is the clause's own remedy. With them it builds a ReceiverController, feeds every accepted packet's CCI into the loss and packet-event inputs, and runs an epoch timer whose decision drives the existing join_channel()/leave_channel() calls. AlcPacket now exposes the CCI it parsed; the field was read off the wire and discarded. Receiver owns this because Receiver is what joins the session, and the clause forbids the join, not the reception. Verification T1: flute_webrc_tests, 116 cases across the suite pass, three of them new (refusal without congestion control, wave-channel count check, short-format CCI read-back). Not in this change The two RECOMMENDED start-up exits of RFC 3738 clause 3.2.2.6, the loss-variable reset on leaving start-up, and clause 3.1.2's first-two-slots deviation, which is a MAY. Leaving a wave channel on the controller's own signal: the controller reports the decision, the receiver acts only on the join side so far.
f20c4ff to
0774caa
Compare
…ntified Problem The congestion control step ran before the TSI was matched against this session's own, so a packet arriving on the session's group and carrying a different session's TSI was fed to this session's congestion controller. The sender-address check was already in the right place, before the parse; the TSI match was not. Observed: twenty packets carrying a foreign TSI, delivered to a live receiver, raised webrc_packets_noted() to 20. It should stay at 0. [observed] Basis RFC 3450 clause 4.5 sets out the receiver's steps as a numbered sequence. Step 2 disposes of a packet whose sender address and TSI do not together match the session. RFC 3450 clause 4.5: "If there is not a match then the packet MUST be discarded without further processing." RFC 3450 clause 4.5, step 3, which is the step this code performs: "The receiver MUST process and act on the CCI field in accordance with the multiple rate congestion control building block." Acting on the CCI of a packet that step 2 required be discarded is further processing of it, so the sequence is load-bearing rather than presentational. On an any-source group, where the kernel filters on the group and not the sender, the earlier order let any host on the network move this receiver's reception rate. Raised by reading the authority during this work Change note_webrc_packet() moves inside the block guarded by the TSI match, which is where step 3 belongs. Receiver gains webrc_packets_noted(), a counter of the packets whose CCI reached the congestion control loop. It exists so the step order can be tested: the loop's own state is private, and without a count of what entered it a test cannot distinguish a packet that was correctly ignored from one that was never delivered. Verification T1: flute_webrc_tests, 121 cases in the suite pass, one new. The new case delivers twenty packets carrying a foreign TSI and then five carrying this session's own, asserting the counter reads 0 and then 5. Against the previous order it reads 20 at the first assertion, so the case measures the order rather than merely exercising the plumbing. Not in this change The two exits from start-up of RFC 3738 clause 3.2.2.6 that this branch has not yet implemented. One of them is required and one recommended, and both are the subject of the commit that follows this one.
…four share
Problem
RFC 3738 clause 3.2.2.6 gives four events that end the start-up period. Two were implemented, a
detected packet loss and the maximum reception rate being reached. Two were not:
- a sharp rise in the delay between joining a wave channel and its first packet arriving, which
the clause makes mandatory;
- the true reception rate failing to rise after a wave was joined, which it recommends.
The clause also attaches a reset of the loss variables to all four events, and that was absent
entirely, so a receiver that left start-up carried forward a loss estimate bearing no relation to
the rate it was actually receiving. src/Webrc.cpp, on_epoch_end() and on_loss_event_begin().
[code-derived]
Basis
RFC 3738 clause 3.2.2.6, the mandatory exit that was missing:
"While SSR_P = infinity the receiver MUST compute, in the notation of Section 3.2.2.2,
differences in successive measurements of (FirstTime-JoinTime) from successive waves and MUST set
SSR_P to max{SSMINR_P, P*TRR_P} when a large increase in (FirstTime-JoinTime) is observed."
Only the size that counts as large is left to the implementer, and the clause recommends one.
RFC 3738 clause 3.2.2.6:
"It is RECOMMENDED that an increase in (FirstTime-JoinTime) be considered large if it exceeds
(P^(NWC+1)-1)/(P*log(P)) / ARR_P."
RFC 3738 clause 3.2.2.6, the recommended exit, in two parts. When to wait:
"While SSR_P = infinity, it is RECOMMENDED that the receiver wait at least one full epoch after
the first packet of a wave is received before joining the next wave."
RFC 3738 clause 3.2.2.6, on what to do if that wait shows no gain:
"If the TRR_P after that full epoch is greatly below ARR_P the receiver SHOULD NOT join and
SHOULD then set SSR_P to max{SSMINR_P, TRR_P}."
RFC 3738 clause 3.2.2.6, the reset the four events share:
"In any of these four cases, the variables associated with LOSSP are reset"
The same sentence names the target: REQN, computed by clause 3.2.2.3 with the current ARTT, is to
come out equal to TRR_P.
The comparison in the recommended exit needs Zeta, whose start-up value the clause also
recommends. RFC 3738 clause 3.2.2.1:
"In start-up mode, it is RECOMMENDED that Beta = (1 - P^(0.25))/2 and Zeta = sqrt(P)/(1 +
sqrt(P))."
Change
The mandatory exit: on_join_measured() keeps the previous wave's join-to-first-packet delay,
differences it against the next, and ends start-up when the rise exceeds the recommended bound.
The recommended exit is split across two methods for one reason. may_join_next_layer() is the
join decision and is const, so it can refuse but cannot record that it refused; it now refuses
both while the newest wave has had less than a full epoch and when the true rate stayed greatly
below the anticipated one. note_start_up_progress(), which the receiver calls once an epoch, is
what then ends start-up.
All four events now end start-up through leave_start_up(), which sets the threshold rate and
performs the shared reset. The reset needs the loss probability that puts REQN at the true
reception rate; REQN falls monotonically as that probability rises and clause 3.2.2.3's
expression has no closed-form inverse, so it is found by bisection over fifty halvings.
A loss event arriving after start-up has already ended does not reset again. The clause scopes the
reset to "any of these four cases", and the four cases are its exits from start-up, not every
later loss.
Receiver reports the first packet of each newly joined wave, since that arrival is what the full
epoch is measured from.
Raised by
reading the authority during this work
Verification
T1: flute_webrc_tests, 128 cases in the suite pass, seven new.
- the mandatory exit, with a rise above the bound and a rise below it;
- the epoch wait, asserted before the epoch has elapsed and again after;
- the true-rate comparison in both directions;
- the shared reset, asserting REQN comes out at the true reception rate;
- a later loss event, asserting it leaves the loss estimate alone.
The three cases covering the recommended exit each hold the other three exits off, by setting the
maximum reception rate out of reach and keeping the anticipated rate below the projection that
would trigger the maximum-rate exit. Without that, an exit firing for the wrong reason would still
pass, and one of these cases was failing that way when first written.
Not in this change
Clause 3.1.2's permission to deviate over a wave's first two time slots, which is a MAY.
Leaving a wave channel on the controller's own signal. The controller reports the decision; the
receiver acts on the join side only, which is recorded as a limitation rather than passed over.
0774caa to
2d16781
Compare
This pull request: keep it in draft. Do not merge it.Status: draft, and it should stay in draft indefinitely. 12 commits, 12 files, +2266/-78. 128 cases pass. It is complete as an implementation and that is precisely why it should not be merged yet. What to do
It changes nothing for 3GPP consumers. One behaviour change to be aware of if it ever does merge. A session outside the 3GPP profiles is now refused at construction when no WEBRC channels are supplied, because RFC 3450 clause 4.5 says a receiver that cannot implement the building block "MUST NOT join the session". That turns a silent violation into an explicit refusal a caller has to resolve. How this set of pull requests came to look like this
Where the nine closed pull requests went
One correction, @rjb1000. #93 is listed above as "already merged in PR #60". #60 merged the FDT growth fix on 17 August and changed no test file; Every open issue, and which pull request closes it
Four open issues are deliberately closed by nothing:
The stack, and the order to merge it inGitHub retargets each child to One trap for whoever merges. The heads live in two repositories: #61, #68 and #98 in |
|
Leave in draft, do not merge. It is complete as an implementation, which is the reason: merging puts an unvalidated rate-control loop into a library others deploy. The passing tests are not conformance. Each checks a formula against the clause stating it. How the loop behaves against a real peer is untestable here. One interoperability test against a second WEBRC implementation would close it. Nothing else is missing. Until then Nothing changes for 3GPP consumers: |
Split out of #62, where the first six of these commits originally landed. They were separated
because they are a self-contained subsystem, they close no issue, they touch nothing either 3GPP
profile uses, and they are the only part of that work that is unverified against a second
implementation. Keeping them there would have forced a reviewer to accept a congestion controller
that cannot be validated here alongside conformance fixes that are ready.
Dependencies and issues
Depends on: #62, and is based on its branch, so #62 merges first.
Paired with: #61, #64, #65 and #68, independent of this one, either order.
Merge order note: this touches
ReceiverandAlcPacket, which #61 and #68 also touch. Whicheverof the three merges last will need its own conflict pass; none of them depends on another's content.
Closes on merge: nothing. No issue tracks this; it is the last mandatory obligation of RFC 3450
clause 2.2 rather than a reported defect.
What this is
The WEBRC congestion control building block, RFC 3738. Twelve commits.
receiver: join and leave a session's multicast channels at runtimetransmitter: carry more than one channel in a sessionadd_channel()/remove_channel(), channel 0 the base channelwebrc: the sender-side schedule of RFC 3738alc: carry Congestion Control Information when a building block supplies ittransmitter: run the WEBRC sender over the session's channelsenable_webrc(), one channel per wave channel, per-channel sequence numberswebrc: the receiver control loop of RFC 3738 clause 3.2webrc: keep the FDT on the base channel, not spread over the waveswebrc: send to each channel at the rate the schedule dictateswebrc: slow start, so the threshold rate stops being infinitereceiver: run WEBRC, and refuse a session that has no congestion controlreceiver: act on the CCI only after the packet's session has been identifiedwebrc: the two remaining exits from start-up, and the loss reset all four shareWhy it exists
RFC 3450 clause 2.2, which binds plain ALC:
RFC 5775 clause 2.2, which binds FLUTE version 2, names the building block:
The last commit acts on the receiver half of the same obligation. RFC 3450 clause 4.5:
and, for a receiver that cannot:
RFC 3451 clause 6.2 obliges the same at the LCT layer.
Receiver's constructor therefore throwsunder
Profile::Unprofiledwhen no WEBRC session channels are supplied, rather than joining asession it cannot run congestion control for. That is a behaviour change for any caller using
Unprofiled: it turns a silent violation into an explicit refusal the caller must resolve.Not for the 3GPP profiles, which refuse it
enable_webrc()throws underProfile::Ts26517andProfile::Ts26346, and those profiles areexempt from the receiver-side refusal above. TS 26.346 clause 7.2.4:
Clause 7.2.7 fixes the CCI at a 32-bit zero besides. So this changes nothing for MBSTF, rt-mbms-mw,
or any other 3GPP consumer: their sessions behave exactly as before, on one channel with a zero CCI,
which is what those clauses require, and they construct
Receiverexactly as they do today.No conformance is claimed, and none should be
The three gaps this pull request originally listed are now closed: the sender weights channels by the
per-slot rates, slow start is implemented, and the receiver controller drives real joins. What
remains is not a list of missing pieces but a structural limit:
A rate-control loop cannot be validated against itself. Every test here checks a formula against
the clause that states it, which catches transcription errors and nothing else. Whether the loop
converges, backs off, or oscillates against a real peer on a real network is untested and untestable
in this repository. RFC 3738 is also Experimental, and wrong congestion control degrades other
people's networks, not only the sender's.
Correction to an earlier version of this description. It listed the two remaining exits from
start-up as RECOMMENDED. One of them is not. RFC 3738 clause 3.2.2.6 on the join-delay exit:
Only the size that counts as large is RECOMMENDED. Both exits, and the loss-variable reset the clause
attaches to all four, are now implemented.
Still absent, and each recorded at its site:
receiver acts on the join side only.
Treat
Profile::Unprofiledand FLUTE version 2 as non-conformant on RFC 3450 clause 2.2 andRFC 5775 clause 2.2 until this has been tested against a second implementation.
What would close it
Test against another implementation. Nothing else is known to be missing.
Verification
T1: 128 cases pass on this branch, 66 of them new. Each formula is checked against the clause that
states it, including the derived quantities of clause 3.1.1 recomputed independently in the test,
exactly N wave channels active in every slot of a full cycle, the rate equation of clause 3.2.2.3,
each mandatory refusal of clause 3.2.3.6 shown separately to change the answer, both slow-start exits
that are implemented, the refusal to join without congestion control, and the clause 5.1 short-format
CCI read back off a hand-built packet.
Two of the newer cases are worth naming because they fail against the code they replace rather than
passing regardless. The step-ordering case sends twenty packets carrying a foreign TSI and then five of
this session's own, asserting that the congestion control loop saw 0 and then 5; against the previous
ordering it sees 20. The three start-up-exit cases each hold the other exits off, so an exit that fired
for the wrong reason shows up as a failed assertion rather than a pass.
What that verification does not cover: any running session, any interaction with a real peer, and
the control loop's behaviour over time. See above.
Change type
New feature, not for 3GPP MBS or MBMS use, and not yet fit to enable in a deployment.